﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.OpenGL;
using Silvermoon.Controls;
using System.Drawing;
using System.Threading;
using Silvermoon.Controls.Sys;
using Silvermoon.Controls.Interfaces;
using Silvermoon.Visuals;
using Microsoft.WindowsCE.Forms;

namespace Silvermoon.Core
{
    /// <summary>
    /// Manages presentation and message exchange of controls.
    /// </summary>
    public sealed class Screen : IDisposable
    {
        #region constants

        /// <summary>
        /// Silvermoon is based on a DPI of 192 which is th standard for displays for 480x800, 480x640 or similiar:
        /// </summary>
        private const float ScaleDPI = 192f;

        #endregion
        #region Fields

        private InputPanel inputPanel;
        private BusyControl cursor;
        private BusyControl contextMenuDelayControl;
        private bool showBusyCursor;
        private TimeEventServer eventTimer;
        private MessageServer messageServer;
        private Size clientSize;
        private List<Control> internalControls;

        #endregion
        #region ctor/dtor

        static Screen()
        {
            ControlScaleFactor = new SizeF(1f, 1f);
            IsScaled = ControlScaleFactor.Width != 1F || ControlScaleFactor.Height != 1F;
            // if (IsScaled) FontSprite.DefaultFontHeight = 9F * ControlScaleFactor.Width;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="screen"/> class.
        /// </summary>
        /// <param name="window">The window.</param>
        public Screen(System.Windows.Forms.Form window)
        {
            if (window == null) throw new ArgumentNullException("window");
            Screen.Current = this;

            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(OnGlobalException);
            Window = window;
            window.Disposed += new EventHandler(OnWindowDisposed);
            window.Resize += new EventHandler(OnResize);

            System.Windows.Forms.Control p = window;
            while (p.Parent != null) p = p.Parent;
            System.Windows.Forms.Form form = p as System.Windows.Forms.Form;
            if (form != null)
            {
                form.Closing += new System.ComponentModel.CancelEventHandler(OnWindowClosing);
                form.Deactivate += new EventHandler(OnDeactivate);
                form.Activated += new EventHandler(OnActivated);
            }
            clientSize = window.ClientSize;

            Device = new Device(window);
            Renderer = new Renderer(Device, window.ClientRectangle);

            internalControls = new List<Control>();

            // A form or control does not get the correct key code data for input, therefore a dummy TextBox is generated that gets
            // the input focus:
            System.Windows.Forms.TextBox tb = new System.Windows.Forms.TextBox();
            tb.Size = new Size(0, 0);
            window.Controls.Add(tb);
            tb.Focus();
            inputPanel = new InputPanel();
            inputPanel.Enabled = false;

            messageServer = new MessageServer(window, tb);
            messageServer.Controls = internalControls;

            eventTimer = new TimeEventServer(this);
            Thread.CurrentThread.Priority = ThreadPriority.BelowNormal;
            eventTimer.Tick += new EventHandler<TimeEventArgs>(OnLoadedTick);
            eventTimer.Enabled = true;
        }


        /// <summary>
        /// Disposes the instance and releases all resources.
        /// You should call this method if you no longer require <see cref="screen"/>.
        /// </summary>
        public void Dispose()
        {
            if (eventTimer != null)
            {
                eventTimer.Enabled = false;
                eventTimer.Dispose();
                eventTimer = null;
            }

            if (Device != null) Device.Dispose();
            Device = null;

            if (cursor != null) cursor.Dispose();
            cursor = null;
            if (contextMenuDelayControl != null) contextMenuDelayControl.Dispose();
            contextMenuDelayControl = null;

            Current = null;
            if (messageServer != null) messageServer.Dispose();
            messageServer = null;
            Renderer = null;
            AppDomain.CurrentDomain.UnhandledException -= OnGlobalException;
        }

        #endregion
        #region Properties

        /// <summary>
        /// Gets whether display is in landscape mode.
        /// </summary>
        public bool Landscape
        {
            get { return clientSize.Width > clientSize.Height; }
        }

        public bool IsBusy
        {
            get { return showBusyCursor; }
            set
            {
                if (showBusyCursor != value) ChangeBusyCursor(value);
            }
        }


        public float TicksPerFrame { get { return eventTimer != null ? eventTimer.TicksPerFrame : 1; } }

        /// <summary>
        /// Gets the width of the render target in pixel.
        /// </summary>
        public int Width { get { return clientSize.Width; } }

        /// <summary>
        /// Gets the height of the render target in pixel.
        /// </summary>
        public int Height { get { return clientSize.Height; } }

        /// <summary>
        /// Gets the size of the rende target in pixel.
        /// </summary>
        public Size Size { get { return clientSize; } }

        public void HideToolTip()
        {
            MessageServer.HideToolTip();
        }


        /// <summary>
        /// Gets the instance. Note that there can be only one screen at one time.
        /// </summary>
        public static Screen Current { get; private set; }

        /// <summary>
        /// Gets the open gl device for rendering.
        /// </summary>
        public Device Device { get; private set; }

        /// <summary>
        /// Gets the <see cref="Renderer"/> class which renders controls.
        /// </summary>
        public Renderer Renderer { get; private set; }

        /// <summary>
        /// Gets the <see cref="T:System.Windows.Forms.Control"/> in which the controls are rendered.
        /// </summary>
        public System.Windows.Forms.Form Window { get; private set; }

        /// <summary>
        /// Gets the scale factor for controls that descibe how to scale the underlying rendering against the specified bounds,
        /// </summary>
        public static SizeF ControlScaleFactor { get; private set; }

        /// <summary>
        /// Gets whether the dimensions of controls are scaled against their underlying graphical visuals and rendering.
        /// </summary>
        public static bool IsScaled { get; private set; }

        public Control FocusedControl { get { return messageServer.MouseFocusControl; } }

        public MessageServer MessageServer { get { return messageServer; } }


        #endregion
        #region Methods

        void OnActivated(object sender, EventArgs e)
        {
            if (eventTimer != null)
            {
                eventTimer.Enabled = true;
                Thread.CurrentThread.Priority = ThreadPriority.BelowNormal;

            }
        }

        void OnDeactivate(object sender, EventArgs e)
        {
            if (eventTimer != null)
            {
                eventTimer.Enabled = false;
                Thread.CurrentThread.Priority = ThreadPriority.Normal;
                //Device.Dispose();
            }
        }

        /// <summary>
        /// Occurs on any unhandled exception.
        /// </summary>
        void OnGlobalException(object sender, UnhandledExceptionEventArgs e)
        {
            Dispose();
        }

        void OnResize(object sender, EventArgs e)
        {
            if (clientSize != Window.ClientSize)
            {
                bool perspective = Renderer.Perspective;
                Color color = Renderer.BackgroundColor;
                Renderer = new Renderer(Device, Window.ClientRectangle);

                Renderer.Perspective = perspective;
                Renderer.BackgroundColor = color;

                clientSize = Window.ClientSize;
                foreach (Control c in internalControls)
                {
                    LayoutEngine.Layout(c, clientSize);
                }
            }
        }

        void OnWindowClosing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            Dispose();
        }

        void OnLoadedTick(object sender, TimeEventArgs e)
        {
            eventTimer.Tick -= OnLoadedTick;
            OnLoaded();
        }

        /// <summary>
        /// Occurs when the window that created the screen is disposed.
        /// </summary>
        void OnWindowDisposed(object sender, EventArgs e)
        {
            Current = null;
            Dispose();
        }

        private void ChangeBusyCursor(bool value)
        {
            showBusyCursor = value;
            if (!value)
            {
                if (this.cursor != null)
                {
                    cursor.Enabled = false;
                    this.Detach(cursor);
                    Invalidate();
                }
            }
            else
            {
                if (cursor == null) cursor = new BusyControl();
                cursor.Location = new Point((Width - cursor.Width) / 2, (Height - cursor.Height) / 2);
                cursor.Visible = true;
                cursor.Enabled = true;
                Attach(cursor);
                Invalidate();
            }
        }

        internal void SetContextMenuDelay(bool visible, Point position)
        {
            if (!visible)
            {
                if (contextMenuDelayControl != null)
                {
                    contextMenuDelayControl.Enabled = false;
                    this.Detach(contextMenuDelayControl);
                }
            }
            else
            {
                if (contextMenuDelayControl == null)
                {
                    contextMenuDelayControl = new BusyControl { ShowDelay = 300 };
                    contextMenuDelayControl.Size = Control.ScaledSize(160, 160);
                    contextMenuDelayControl.Alpha = 180;
                    contextMenuDelayControl.Invalidate(InvalidationFlag.All);
                }
                contextMenuDelayControl.Location = new Point(position.X - contextMenuDelayControl.Width / 2, position.Y - contextMenuDelayControl.Height / 2);
                contextMenuDelayControl.Visible = true;
                contextMenuDelayControl.Enabled = true;
                Attach(contextMenuDelayControl);
            }
        }

        /// <summary>
        /// Invalidates a control. If a control is Invalid, it must be rendered again.
        /// </summary>
        public void Invalidate()
        {
            Renderer.Invalidated = true;
        }

        /// <summary>
        /// Renders all controls.
        /// </summary>
        public void Render()
        {
            if (Renderer != null && Renderer.Invalidated)
            {
                Renderer.Render(internalControls);
            }
        }

        public bool IsRendering { get { return Renderer != null ? Renderer.IsRendering : false; } }

        /// <summary>
        /// Creates the only instance.
        /// </summary>
        /// <param name="window">The form in which the controls are presented.</param>
        /// <returns>The  <see cref="screen"/> instance.</returns>
        public static Screen Initialize(System.Windows.Forms.Form window)
        {
            // the base dpi for Silvermoon are 192 dpi, other displays must be scaled:
            float scaleX = ((float)window.CurrentAutoScaleDimensions.Width) / ScaleDPI;
            float scaleY = ((float)window.CurrentAutoScaleDimensions.Height) / ScaleDPI;
            Screen.ControlScaleFactor = new SizeF(scaleX, scaleY);
            IsScaled = ControlScaleFactor.Width != 1F || ControlScaleFactor.Height != 1F;

            if (Current != null) throw new SystemException("Instance is already initialized.");
            Current = new Screen(window);
            return Current;
        }

        /// <summary>
        /// sets the focus to the specified control.
        /// </summary>
        /// <param name="control">The control to set the focus to.</param>
        /// <returns>True if the control has got the focus, otherwhise false (which means that it is either not in the tree or not enabled or visible).</returns>
        public bool Focus(Control control)
        {
            return messageServer.Focus(control);
        }

        public static void RenderCurrent()
        {
            if (Current != null) Current.Render();
        }


        internal bool HasFocus(Control control)
        {
            return messageServer.HasFocus(control);
        }

        void OnLoaded()
        {
            if (Loaded != null) Loaded(this, EventArgs.Empty);
        }

        /// <summary>
        /// Forces the event timer to progress. this is used in synchronous animations or transitions to ensure that
        ///  asynchrounous animations are also progressed while the synchronous animation is being executed in a loop.
        /// </summary>
        internal void ProgressTimer()
        {
            eventTimer.Progress();
        }

        /// <summary>
        /// Gets the boundaries of the control in absolute pixels relative to the screen.
        /// Note that this is a calcualted value that can take a lot of recursive  loops to determine, so cache it if possible!
        /// </summary>
        /// <returns>A rectangle that contains the screen boundaries.</returns>
        public Rectangle ToScreenBounds(Control control)
        {
            Point p = new Point(control.X, control.Y);
            if (GetScreenBounds(control, internalControls, ref p))
            {
                return new Rectangle(p.X, p.Y, control.Width, control.Height);
            }
            else return Rectangle.Empty;
        }

        private bool GetScreenBounds(Control control, IEnumerable<Control> controls, ref Point p)
        {
            foreach (Control c in controls)
            {
                if (c == control) return true;
                else
                {
                    if (c.SubControls != null)
                    {
                        Point cpy = new Point(p.X + c.X, p.Y + c.Y);
                        if (GetScreenBounds(control, c.SubControls, ref cpy))
                        {
                            p = cpy;
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        /// <summary>
        /// Add a control to the end of the list if not already added.
        /// </summary>
        /// <param name="menu"></param>
        internal void Attach(Control control)
        {
            if (!internalControls.Contains(control))
            {
                internalControls.Add(control);
                LayoutEngine.Layout(control, Size);
            }
        }


        /// <summary>
        /// Removes a control.
        /// </summary>
        /// <param name="menu"></param>
        /// <returns></returns>
        internal void Detach(Control control)
        {
            internalControls.Remove(control);
        }

        public void Progess()
        {
            eventTimer.Progress();
        }

        /// <summary>
        /// Closes the application.
        /// </summary>
        public void Close()
        {
            (Window as System.Windows.Forms.Form).Close();
        }

        internal void SendToBack(Window window)
        {
            internalControls.Remove(window);
            internalControls.Insert(0, window);
        }

        internal void BringToFront(Window window)
        {
            internalControls.Remove(window);
            internalControls.Add(window);
        }

        /// <summary>
        /// Captures the specified control and returns a texture that contains the complete image of the control.
        /// </summary>
        /// <param name="c">The control to capture.</param>
        /// <returns>A texture that contains the image of the specified control, otherwise null.</returns>
        public Texture Capture(Control c)
        {
            return Renderer.Capture(c);
        }

        private bool GetAncestors(Control find, Control control, IList<Control> ancestors)
        {
            if (find == control) return true;
            IEnumerable<Control> childControls = control.SubControls;
            if (childControls != null)
            {
                foreach (Control c in childControls)
                {
                    ancestors.Add(c);
                    if (GetAncestors(find, c, ancestors)) return true;
                    ancestors.RemoveAt(ancestors.Count - 1);
                }
            }
            return false;
        }


        /// <summary>
        /// Gets all ancestors of the specified control beginning from the root to grandparent, parent.
        /// </summary>
        /// <param name="control">The control for which to get all ancestors.</param>
        /// <returns>An enumeration of <see cref="T:Control"/>.</returns>
        public IEnumerable<Control> GetAncestors(Control control)
        {
            List<Control> controls = new List<Control>();
            foreach (Control c in internalControls)
            {
                controls.Add(c);
                if (GetAncestors(control, c, controls)) return controls;
                controls.RemoveAt(controls.Count - 1);
            }
            return controls.ToArray();
        }

        /// <summary>
        /// Gets the actual TextColor for the specifed control.
        /// </summary>
        /// <param name="control">The control for which to get the TextColor</param>
        /// <returns>The TextColor for the control.</returns>
        /// <remarks>TextColor property is special since it is an entailed property.</remarks>
        public Color GetTextColor(Control control)
        {
            Color textColor = Renderer.StartTextColor;
            IEnumerable<Control> ancestors = GetAncestors(control);
            foreach (Control c in ancestors)
            {
                Color color = c.TextColor;
                if (!color.IsEmpty) textColor = color;
            }

            return textColor;
        }

        public void HideInputPanel()
        {
            inputPanel.Enabled = false;
        }

        #endregion
        #region events

        /// <summary>
        /// Occurs once when rendering is available and rendering appeared the very first time.
        /// </summary>
        public event EventHandler Loaded;

        public event EventHandler<TimeEventArgs> Tick
        {
            add
            {
                eventTimer.Enabled = true;
                eventTimer.Tick += value;
            }
            remove
            {
                if (eventTimer != null) eventTimer.Tick -= value;
            }
        }

        /// <summary>
        /// Occurs when the input focus changes.
        /// </summary>
        public event EventHandler InputFocusChanged
        {
            add { messageServer.InputFocusChanged += value; }
            remove { messageServer.InputFocusChanged -= value; }
        }

        /// <summary>
        /// Occurs when the input mouse changes.
        /// </summary>
        public event EventHandler MouseFocusChanged
        {
            add { messageServer.MouseFocusChanged += value; }
            remove { messageServer.MouseFocusChanged -= value; }
        }

        #endregion
    }
}
